/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2017 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockManager.h"
#include "QxDockAreaTitleBar.h"
#include "QxDockAreaWidget.h"
#include "QxDockFocusController.h"
#include "QxDockOverlay.h"
#include "QxDockSplitter.h"
#include "QxDockWidget.h"
#include "QxDockWidgetTab.h"
#include "QxDockStateReader.h"
#include "QxDockFloatingContainer.h"
#include "QxDockIconProvider.h"
#include "QxDockAutoHideContainer.h"

#include <QAction>
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QList>
#include <QMainWindow>
#include <QMap>
#include <QMenu>
#include <QSettings>
#include <QVariant>
#include <QWindow>
#include <QWindowStateChangeEvent>
#include <QXmlStreamWriter>

#include <algorithm>
#include <iostream>

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#include "linux/QxDockFloatingWidgetTitleBar.h"
#endif

/**
 * Initializes the resources specified by the .qrc file with the specified base
 * name. Normally, when resources are built as part of the application, the
 * resources are loaded automatically at startup. The Q_INIT_RESOURCE() macro
 * is necessary on some platforms for resources stored in a static library.
 * Because GCC causes a linker error if we put Q_INIT_RESOURCE into the
 * loadStyleSheet() function, we place it into a function outside of the dock
 * namespace
 */
static void initResource()
{
    Q_INIT_RESOURCE(dock);
}

QX_BEGIN_NAMESPACE

/**
 * Internal file version in case the structure changes internally
 */
enum eStateFileVersion {
    InitialVersion = 0,         //!< InitialVersion
    Version1 = 1,               //!< Version1
    CurrentVersion = Version1   //!< CurrentVersion
};

static DockManager::ConfigFlags StaticConfigFlags = DockManager::DefaultNonOpaqueConfig;
static DockManager::AutoHideFlags StaticAutoHideConfigFlags;   // auto hide feature is disabled by default

static QString FloatingContainersTitle;

/**
 * Private data class of DockManager class (pimpl)
 */
struct DockManagerPrivate {
    DockManager *_this;
    QList<DockFloatingContainer *> FloatingWidgets;
    QList<DockFloatingContainer *> HiddenFloatingWidgets;
    QList<DockContainerWidget *> Containers;
    DockOverlay *ContainerOverlay;
    DockOverlay *DockAreaOverlay;
    QMap<QString, DockWidget *> DockWidgetsMap;
    QMap<QString, QByteArray> Perspectives;
    QMap<QString, QMenu *> ViewMenuGroups;
    QMenu *ViewMenu;
    DockManager::eViewMenuInsertionOrder MenuInsertionOrder = DockManager::MenuAlphabeticallySorted;
    bool RestoringState = false;
    QVector<DockFloatingContainer *> UninitializedFloatingWidgets;
    DockFocusController *FocusController = nullptr;
    DockWidget *CentralWidget = nullptr;
    bool IsLeavingMinimized = false;

    /**
     * Private data constructor
     */
    DockManagerPrivate(DockManager *_public);

    /**
     * Checks if the given data stream is a valid docking system state
     * file.
     */
    bool checkFormat(const QByteArray &state, int version);

    /**
     * Restores the state
     */
    bool restoreStateFromXml(const QByteArray &state, int version, bool Testing = internal::Restore);

    /**
     * Restore state
     */
    bool restoreState(const QByteArray &state, int version);

    void restoreDockWidgetsOpenState();
    void restoreDockAreasIndices();
    void emitTopLevelEvents();

    void hideFloatingWidgets()
    {
        // Hide updates of floating widgets from user
        for (auto FloatingWidget : FloatingWidgets) {
            FloatingWidget->hide();
        }
    }

    void markDockWidgetsDirty()
    {
        for (auto dockWidget : DockWidgetsMap) {
            dockWidget->setProperty(internal::DirtyProperty, true);
        }
    }

    /**
     * Restores the container with the given index
     */
    bool restoreContainer(int Index, DockStateReader &stream, bool Testing);

    /**
     * Loads the stylesheet
     */
    void loadStylesheet();

    /**
     * Adds action to menu - optionally in sorted order
     */
    void addActionToMenu(QAction *Action, QMenu *Menu, bool InsertSorted);
};
// struct DockManagerPrivate

DockManagerPrivate::DockManagerPrivate(DockManager *_public) : _this(_public)
{
}

void DockManagerPrivate::loadStylesheet()
{
    initResource();
    QString Result;
    QString FileName = ":dock/stylesheets/";
    FileName += DockManager::testConfigFlag(DockManager::FocusHighlighting) ? "focus_highlighting" : "default";
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    FileName += "_linux";
#endif
    FileName += ".css";
    QFile StyleSheetFile(FileName);
    StyleSheetFile.open(QIODevice::ReadOnly);
    QTextStream StyleSheetStream(&StyleSheetFile);
    Result = StyleSheetStream.readAll();
    StyleSheetFile.close();
    _this->setStyleSheet(Result);
}

bool DockManagerPrivate::restoreContainer(int Index, DockStateReader &stream, bool Testing)
{
    if (Testing) {
        Index = 0;
    }

    bool Result = false;
    if (Index >= Containers.count()) {
        DockFloatingContainer *FloatingWidget = new DockFloatingContainer(_this);
        Result = FloatingWidget->restoreState(stream, Testing);
    } else {
        QX_DOCK_PRINT("d->Containers[i]->restoreState ");
        auto Container = Containers[Index];
        if (Container->isFloating()) {
            Result = Container->floatingWidget()->restoreState(stream, Testing);
        } else {
            Result = Container->restoreState(stream, Testing);
        }
    }

    return Result;
}

bool DockManagerPrivate::checkFormat(const QByteArray &state, int version)
{
    return restoreStateFromXml(state, version, internal::RestoreTesting);
}

bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int version, bool Testing)
{
    Q_UNUSED(version);

    if (state.isEmpty()) {
        return false;
    }
    DockStateReader s(state);
    s.readNextStartElement();
    if (s.name() != QLatin1String("QtAdvancedDockingSystem")) {
        return false;
    }
    QX_DOCK_PRINT(s.attributes().value("Version"));
    bool ok;
    int v = s.attributes().value("Version").toInt(&ok);
    if (!ok || v > CurrentVersion) {
        return false;
    }
    s.setFileVersion(v);

    QX_DOCK_PRINT(s.attributes().value("UserVersion"));
    // Older files do not support UserVersion but we still want to load them so
    // we first test if the attribute exists
    if (!s.attributes().value("UserVersion").isEmpty()) {
        v = s.attributes().value("UserVersion").toInt(&ok);
        if (!ok || v != version) {
            return false;
        }
    }

    bool Result = true;
#ifdef QX_DOCK_DEBUG_PRINT
    int DockContainers = s.attributes().value("Containers").toInt();
#endif
    QX_DOCK_PRINT(DockContainers);

    if (CentralWidget) {
        const auto CentralWidgetAttribute = s.attributes().value("CentralWidget");
        // If we have a central widget but a state without central widget, then
        // something is wrong.
        if (CentralWidgetAttribute.isEmpty()) {
            qWarning() << "Dock manager has central widget but saved state does not have central widget.";
            return false;
        }

        // If the object name of the central widget does not match the name of the
        // saved central widget, the something is wrong
        if (CentralWidget->objectName() != CentralWidgetAttribute.toString()) {
            qWarning() << "Object name of central widget does not match name of central widget in saved state.";
            return false;
        }
    }

    int DockContainerCount = 0;
    while (s.readNextStartElement()) {
        if (s.name() == QLatin1String("Container")) {
            Result = restoreContainer(DockContainerCount, s, Testing);
            if (!Result) {
                break;
            }
            DockContainerCount++;
        }
    }

    if (!Testing) {
        // Delete remaining empty floating widgets
        int FloatingWidgetIndex = DockContainerCount - 1;
        for (int i = FloatingWidgetIndex; i < FloatingWidgets.count(); ++i) {
            auto *floatingWidget = FloatingWidgets[i];
            _this->removeDockContainer(floatingWidget->dockContainer());
            floatingWidget->deleteLater();
        }
    }

    return Result;
}

void DockManagerPrivate::restoreDockWidgetsOpenState()
{
    // All dock widgets, that have not been processed in the restore state
    // function are invisible to the user now and have no assigned dock area
    // They do not belong to any dock container, until the user toggles the
    // toggle view action the next time
    for (auto dockWidget : DockWidgetsMap) {
        if (dockWidget->property(internal::DirtyProperty).toBool()) {
            // If the dockWidget is an auto hide widget that is not assigned yet,
            // then we need to delete the auto hide container now
            if (dockWidget->isAutoHide()) {
                dockWidget->autoHideDockContainer()->cleanupAndDelete();
            }
            dockWidget->flagAsUnassigned();
            Q_EMIT dockWidget->viewToggled(false);
        } else {
            dockWidget->toggleViewInternal(!dockWidget->property(internal::ClosedProperty).toBool());
        }
    }
}

void DockManagerPrivate::restoreDockAreasIndices()
{
    // Now all dock areas are properly restored and we setup the index of
    // The dock areas because the previous toggleView() action has changed
    // the dock area index
    int Count = 0;
    for (auto DockContainer : Containers) {
        Count++;
        for (int i = 0; i < DockContainer->dockAreaCount(); ++i) {
            DockAreaWidget *dockArea = DockContainer->dockArea(i);
            QString DockWidgetName = dockArea->property("currentDockWidget").toString();
            DockWidget *dockWidget = nullptr;
            if (!DockWidgetName.isEmpty()) {
                dockWidget = _this->findDockWidget(DockWidgetName);
            }

            if (!dockWidget || dockWidget->isClosed()) {
                int Index = dockArea->indexOfFirstOpenDockWidget();
                if (Index < 0) {
                    continue;
                }
                dockArea->setCurrentIndex(Index);
            } else {
                dockArea->internalSetCurrentDockWidget(dockWidget);
            }
        }
    }
}

void DockManagerPrivate::emitTopLevelEvents()
{
    // Finally we need to send the topLevelChanged() signals for all dock
    // widgets if top level changed
    for (auto DockContainer : Containers) {
        DockWidget *TopLevelDockWidget = DockContainer->topLevelDockWidget();
        if (TopLevelDockWidget) {
            TopLevelDockWidget->emitTopLevelChanged(true);
        } else {
            for (int i = 0; i < DockContainer->dockAreaCount(); ++i) {
                auto dockArea = DockContainer->dockArea(i);
                for (auto dockWidget : dockArea->dockWidgets()) {
                    dockWidget->emitTopLevelChanged(false);
                }
            }
        }
    }
}

bool DockManagerPrivate::restoreState(const QByteArray &State, int version)
{
    QByteArray state = State.startsWith("<?xml") ? State : qUncompress(State);
    if (!checkFormat(state, version)) {
        QX_DOCK_PRINT("checkFormat: Error checking format!!!!!!!");
        return false;
    }

    // Hide updates of floating widgets from use
    hideFloatingWidgets();
    markDockWidgetsDirty();

    if (!restoreStateFromXml(state, version)) {
        QX_DOCK_PRINT("restoreState: Error restoring state!!!!!!!");
        return false;
    }

    restoreDockWidgetsOpenState();
    restoreDockAreasIndices();
    emitTopLevelEvents();
    _this->dumpLayout();

    return true;
}

void DockManagerPrivate::addActionToMenu(QAction *Action, QMenu *Menu, bool InsertSorted)
{
    if (InsertSorted) {
        auto Actions = Menu->actions();
        auto it = std::find_if(Actions.begin(), Actions.end(), [&Action](const QAction *a) {
            return a->text().compare(Action->text(), Qt::CaseInsensitive) > 0;
        });

        if (it == Actions.end()) {
            Menu->addAction(Action);
        } else {
            Menu->insertAction(*it, Action);
        }
    } else {
        Menu->addAction(Action);
    }
}

DockManager::DockManager(QWidget *parent) : DockContainerWidget(this, parent), d(new DockManagerPrivate(this))
{
    createRootSplitter();
    createSideTabBarWidgets();
    QMainWindow *MainWindow = qobject_cast<QMainWindow *>(parent);
    if (MainWindow) {
        MainWindow->setCentralWidget(this);
    }

    d->ViewMenu = new QMenu(tr("Show View"), this);
    d->DockAreaOverlay = new DockOverlay(this, DockOverlay::ModeDockAreaOverlay);
    d->ContainerOverlay = new DockOverlay(this, DockOverlay::ModeContainerOverlay);
    d->Containers.append(this);
    d->loadStylesheet();

    if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) {
        d->FocusController = new DockFocusController(this);
    }

    window()->installEventFilter(this);

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
    connect(qApp, &QApplication::focusWindowChanged, [](QWindow *focusWindow) {
        // bring modal dialogs to foreground to ensure that they are in front of any
        // floating dock widget
        if (focusWindow && focusWindow->isModal()) {
            focusWindow->raise();
        }
    });
#endif
}

DockManager::~DockManager()
{
    // fix memory leaks, see https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/307
    std::vector<Qx::DockAreaWidget *> areas;
    for (int i = 0; i != dockAreaCount(); ++i) {
        areas.push_back(dockArea(i));
    }
    for (auto area : areas) {
        for (auto widget : area->dockWidgets())
            delete widget;

        delete area;
    }

    auto FloatingWidgets = d->FloatingWidgets;
    for (auto FloatingWidget : FloatingWidgets) {
        delete FloatingWidget;
    }

    delete d;
}

#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
bool DockManager::eventFilter(QObject *obj, QEvent *e)
{
    // Emulate Qt:Tool behaviour.
    // Required because on some WMs Tool windows can't be maximized.

    // Window always on top of the MainWindow.
    if (e->type() == QEvent::WindowActivate) {
        for (auto _window : floatingWidgets()) {
            if (!_window->isVisible() || window()->isMinimized()) {
                continue;
            }
            // setWindowFlags(Qt::WindowStaysOnTopHint) will hide the window and thus requires a show call.
            // This then leads to flickering and a nasty endless loop (also buggy behaviour on Ubuntu).
            // So we just do it ourself.
            if (QGuiApplication::platformName() == QLatin1String("xcb")) {
                internal::xcb_update_prop(true, _window->window()->winId(), "_NET_WM_STATE", "_NET_WM_STATE_ABOVE",
                                          "_NET_WM_STATE_STAYS_ON_TOP");
            } else {
                _window->setWindowFlag(Qt::WindowStaysOnTopHint, true);
            }
        }
    } else if (e->type() == QEvent::WindowDeactivate) {
        for (auto _window : floatingWidgets()) {
            if (!_window->isVisible() || window()->isMinimized()) {
                continue;
            }

            if (QGuiApplication::platformName() == QLatin1String("xcb")) {
                internal::xcb_update_prop(false, _window->window()->winId(), "_NET_WM_STATE", "_NET_WM_STATE_ABOVE",
                                          "_NET_WM_STATE_STAYS_ON_TOP");
            } else {
                _window->setWindowFlag(Qt::WindowStaysOnTopHint, false);
            }
            _window->raise();
        }
    }

    // Sync minimize with MainWindow
    if (e->type() == QEvent::WindowStateChange) {
        for (auto _window : floatingWidgets()) {
            if (!_window->isVisible()) {
                continue;
            }

            if (window()->isMinimized()) {
                _window->showMinimized();
            } else {
                _window->setWindowState(_window->windowState() & (~Qt::WindowMinimized));
            }
        }
        if (!window()->isMinimized()) {
            QApplication::setActiveWindow(window());
        }
    }
    return Super::eventFilter(obj, e);
}
#else

bool DockManager::eventFilter(QObject *obj, QEvent *e)
{
    if (e->type() == QEvent::WindowStateChange) {
        QWindowStateChangeEvent *ev = static_cast<QWindowStateChangeEvent *>(e);
        if (ev->oldState().testFlag(Qt::WindowMinimized)) {
            d->IsLeavingMinimized = true;
            QMetaObject::invokeMethod(this, "endLeavingMinimizedState", Qt::QueuedConnection);
        }
    }
    return Super::eventFilter(obj, e);
}
#endif

void DockManager::endLeavingMinimizedState()
{
    d->IsLeavingMinimized = false;
    this->activateWindow();
}

bool DockManager::isLeavingMinimizedState() const
{
    return d->IsLeavingMinimized;
}

void DockManager::registerFloatingWidget(DockFloatingContainer *FloatingWidget)
{
    d->FloatingWidgets.append(FloatingWidget);
    Q_EMIT floatingWidgetCreated(FloatingWidget);
    QX_DOCK_PRINT("d->FloatingWidgets.count() " << d->FloatingWidgets.count());
}

void DockManager::removeFloatingWidget(DockFloatingContainer *FloatingWidget)
{
    d->FloatingWidgets.removeAll(FloatingWidget);
}

void DockManager::registerDockContainer(DockContainerWidget *DockContainer)
{
    d->Containers.append(DockContainer);
}

void DockManager::removeDockContainer(DockContainerWidget *DockContainer)
{
    if (this != DockContainer) {
        d->Containers.removeAll(DockContainer);
    }
}

DockOverlay *DockManager::containerOverlay() const
{
    return d->ContainerOverlay;
}

DockOverlay *DockManager::dockAreaOverlay() const
{
    return d->DockAreaOverlay;
}

const QList<DockContainerWidget *> DockManager::dockContainers() const
{
    return d->Containers;
}

const QList<DockFloatingContainer *> DockManager::floatingWidgets() const
{
    return d->FloatingWidgets;
}

unsigned int DockManager::zOrderIndex() const
{
    return 0;
}

QByteArray DockManager::saveState(int version) const
{
    QByteArray xmldata;
    QXmlStreamWriter s(&xmldata);
    auto ConfigFlags = DockManager::configFlags();
    s.setAutoFormatting(ConfigFlags.testFlag(XmlAutoFormattingEnabled));
    s.writeStartDocument();
    s.writeStartElement("QtAdvancedDockingSystem");
    s.writeAttribute("Version", QString::number(CurrentVersion));
    s.writeAttribute("UserVersion", QString::number(version));
    s.writeAttribute("Containers", QString::number(d->Containers.count()));
    if (d->CentralWidget) {
        s.writeAttribute("CentralWidget", d->CentralWidget->objectName());
    }
    for (auto Container : d->Containers) {
        Container->saveState(s);
    }

    s.writeEndElement();
    s.writeEndDocument();

    return ConfigFlags.testFlag(XmlCompressionEnabled) ? qCompress(xmldata, 9) : xmldata;
}

bool DockManager::restoreState(const QByteArray &state, int version)
{
    // Prevent multiple calls as long as state is not restore. This may
    // happen, if QApplication::processEvents() is called somewhere
    if (d->RestoringState) {
        return false;
    }

    // We hide the complete dock manager here. Restoring the state means
    // that DockWidgets are removed from the dockArea internal stack layout
    // which in turn  means, that each time a widget is removed the stack
    // will show and raise the next available widget which in turn
    // triggers show events for the dock widgets. To avoid this we hide the
    // dock manager. Because there will be no processing of application
    // events until this function is finished, the user will not see this
    // hiding
    bool IsHidden = this->isHidden();
    if (!IsHidden) {
        hide();
    }
    d->RestoringState = true;
    Q_EMIT restoringState();
    bool Result = d->restoreState(state, version);
    d->RestoringState = false;
    if (!IsHidden) {
        show();
    }
    Q_EMIT stateRestored();
    return Result;
}

DockFloatingContainer *DockManager::addDockWidgetFloating(DockWidget *dockwidget)
{
    d->DockWidgetsMap.insert(dockwidget->objectName(), dockwidget);
    DockAreaWidget *OldDockArea = dockwidget->dockAreaWidget();
    if (OldDockArea) {
        OldDockArea->removeDockWidget(dockwidget);
    }

    dockwidget->setDockManager(this);
    DockFloatingContainer *FloatingWidget = new DockFloatingContainer(dockwidget);
    FloatingWidget->resize(dockwidget->size());
    if (isVisible()) {
        FloatingWidget->show();
    } else {
        d->UninitializedFloatingWidgets.append(FloatingWidget);
    }
    Q_EMIT dockWidgetAdded(dockwidget);
    return FloatingWidget;
}

void DockManager::showEvent(QShowEvent *event)
{
    Super::showEvent(event);

    // Fix Issue #380
    restoreHiddenFloatingWidgets();
    if (d->UninitializedFloatingWidgets.empty()) {
        return;
    }

    for (auto FloatingWidget : d->UninitializedFloatingWidgets) {
        // Check, if someone closed a floating dock widget before the dock
        // manager is shown
        if (FloatingWidget->dockContainer()->hasOpenDockAreas()) {
            FloatingWidget->show();
        }
    }
    d->UninitializedFloatingWidgets.clear();
}

void DockManager::restoreHiddenFloatingWidgets()
{
    if (d->HiddenFloatingWidgets.isEmpty()) {
        return;
    }

    // Restore floating widgets that were hidden upon hideManagerAndFloatingWidgets
    for (auto FloatingWidget : d->HiddenFloatingWidgets) {
        bool hasDockWidgetVisible = false;

        // Needed to prevent DockFloatingContainer being shown empty
        // Could make sense to move this to DockFloatingContainer::showEvent(QShowEvent *event)
        // if experiencing DockFloatingContainer being shown empty in other situations, but let's keep
        // it here for now to make sure changes to fix Issue #380 does not impact existing behaviours
        for (auto dockWidget : FloatingWidget->dockWidgets()) {
            if (dockWidget->toggleViewAction()->isChecked()) {
                dockWidget->toggleView(true);
                hasDockWidgetVisible = true;
            }
        }

        if (hasDockWidgetVisible) {
            FloatingWidget->show();
        }
    }

    d->HiddenFloatingWidgets.clear();
}

DockAreaWidget *DockManager::addDockWidget(DockWidgetArea area, DockWidget *dockwidget,
                                             DockAreaWidget *dockAreaWidget, int Index)
{
    d->DockWidgetsMap.insert(dockwidget->objectName(), dockwidget);
    auto Container = dockAreaWidget ? dockAreaWidget->dockContainer() : this;
    auto AreaOfAddedDockWidget = Container->addDockWidget(area, dockwidget, dockAreaWidget, Index);
    Q_EMIT dockWidgetAdded(dockwidget);
    return AreaOfAddedDockWidget;
}

DockAreaWidget *DockManager::addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockwidget,
                                                        DockContainerWidget *dockContainerWidget)
{
    d->DockWidgetsMap.insert(dockwidget->objectName(), dockwidget);
    auto AreaOfAddedDockWidget = dockContainerWidget->addDockWidget(area, dockwidget);
    Q_EMIT dockWidgetAdded(dockwidget);
    return AreaOfAddedDockWidget;
}

DockAutoHideContainer *DockManager::addAutoHideDockWidget(SideBarLocation area, DockWidget *dockwidget)
{
    return addAutoHideDockWidgetToContainer(area, dockwidget, this);
}

DockAutoHideContainer *DockManager::addAutoHideDockWidgetToContainer(SideBarLocation area, DockWidget *dockwidget,
                                                                       DockContainerWidget *dockContainerWidget)
{
    d->DockWidgetsMap.insert(dockwidget->objectName(), dockwidget);
    auto container = dockContainerWidget->createAndSetupAutoHideContainer(area, dockwidget);
    container->collapseView(true);

    Q_EMIT dockWidgetAdded(dockwidget);
    return container;
}

DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *dockwidget)
{
    DockAreaWidget *AreaWidget = lastAddedDockAreaWidget(area);
    if (AreaWidget) {
        return addDockWidget(Qx::CenterDockWidgetArea, dockwidget, AreaWidget);
    } else {
        return addDockWidget(area, dockwidget, nullptr);
    }
}

DockAreaWidget *DockManager::addDockWidgetTabToArea(DockWidget *dockwidget, DockAreaWidget *dockAreaWidget,
                                                      int Index)
{
    return addDockWidget(Qx::CenterDockWidgetArea, dockwidget, dockAreaWidget, Index);
}

DockWidget *DockManager::findDockWidget(const QString &ObjectName) const
{
    return d->DockWidgetsMap.value(ObjectName, nullptr);
}

void DockManager::removeDockWidget(DockWidget *dockwidget)
{
    Q_EMIT dockWidgetAboutToBeRemoved(dockwidget);
    d->DockWidgetsMap.remove(dockwidget->objectName());
    DockContainerWidget::removeDockWidget(dockwidget);
    dockwidget->setDockManager(nullptr);
    Q_EMIT dockWidgetRemoved(dockwidget);
}

QMap<QString, DockWidget *> DockManager::dockWidgetsMap() const
{
    return d->DockWidgetsMap;
}

void DockManager::addPerspective(const QString &UniquePrespectiveName)
{
    d->Perspectives.insert(UniquePrespectiveName, saveState());
    Q_EMIT perspectiveListChanged();
}

void DockManager::removePerspective(const QString &Name)
{
    removePerspectives({Name});
}

void DockManager::removePerspectives(const QStringList &Names)
{
    int Count = 0;
    for (const auto &Name : Names) {
        Count += d->Perspectives.remove(Name);
    }

    if (Count) {
        Q_EMIT perspectivesRemoved();
        Q_EMIT perspectiveListChanged();
    }
}

QStringList DockManager::perspectiveNames() const
{
    return d->Perspectives.keys();
}

void DockManager::openPerspective(const QString &PerspectiveName)
{
    const auto Iterator = d->Perspectives.find(PerspectiveName);
    if (d->Perspectives.end() == Iterator) {
        return;
    }

    Q_EMIT openingPerspective(PerspectiveName);
    restoreState(Iterator.value());
    Q_EMIT perspectiveOpened(PerspectiveName);
}

void DockManager::savePerspectives(QSettings &Settings) const
{
    Settings.beginWriteArray("Perspectives", d->Perspectives.size());
    int i = 0;
    for (auto it = d->Perspectives.constBegin(); it != d->Perspectives.constEnd(); ++it) {
        Settings.setArrayIndex(i);
        Settings.setValue("Name", it.key());
        Settings.setValue("State", it.value());
        ++i;
    }
    Settings.endArray();
}

void DockManager::loadPerspectives(QSettings &Settings)
{
    d->Perspectives.clear();
    int Size = Settings.beginReadArray("Perspectives");
    if (!Size) {
        Settings.endArray();
        return;
    }

    for (int i = 0; i < Size; ++i) {
        Settings.setArrayIndex(i);
        QString Name = Settings.value("Name").toString();
        QByteArray Data = Settings.value("State").toByteArray();
        if (Name.isEmpty() || Data.isEmpty()) {
            continue;
        }

        d->Perspectives.insert(Name, Data);
    }

    Settings.endArray();
    Q_EMIT perspectiveListChanged();
    Q_EMIT perspectiveListLoaded();
}

DockWidget *DockManager::centralWidget() const
{
    return d->CentralWidget;
}

DockAreaWidget *DockManager::setCentralWidget(DockWidget *widget)
{
    if (!widget) {
        d->CentralWidget = nullptr;
        return nullptr;
    }

    // Setting a new central widget is now allowed if there is already a central
    // widget or if there are already other dock widgets
    if (d->CentralWidget) {
        qWarning("Setting a central widget not possible because there is already a central widget.");
        return nullptr;
    }

    // Setting a central widget is now allowed if there are already other
    // dock widgets.
    if (!d->DockWidgetsMap.isEmpty()) {
        qWarning("Setting a central widget not possible - the central widget need to be the first "
                 "dock widget that is added to the dock manager.");
        return nullptr;
    }

    widget->setFeature(DockWidget::DockWidgetClosable, false);
    widget->setFeature(DockWidget::DockWidgetMovable, false);
    widget->setFeature(DockWidget::DockWidgetFloatable, false);
    widget->setFeature(DockWidget::DockWidgetPinnable, false);
    d->CentralWidget = widget;
    DockAreaWidget *CentralArea = addDockWidget(CenterDockWidgetArea, widget);
    CentralArea->setDockAreaFlag(DockAreaWidget::eDockAreaFlag::HideSingleWidgetTitleBar, true);
    return CentralArea;
}

QAction *DockManager::addToggleViewActionToMenu(QAction *ToggleViewAction, const QString &Group,
                                                 const QIcon &GroupIcon)
{
    bool AlphabeticallySorted = (MenuAlphabeticallySorted == d->MenuInsertionOrder);
    if (!Group.isEmpty()) {
        QMenu *GroupMenu = d->ViewMenuGroups.value(Group, 0);
        if (!GroupMenu) {
            GroupMenu = new QMenu(Group, this);
            GroupMenu->setIcon(GroupIcon);
            d->addActionToMenu(GroupMenu->menuAction(), d->ViewMenu, AlphabeticallySorted);
            d->ViewMenuGroups.insert(Group, GroupMenu);
        } else if (GroupMenu->icon().isNull() && !GroupIcon.isNull()) {
            GroupMenu->setIcon(GroupIcon);
        }

        d->addActionToMenu(ToggleViewAction, GroupMenu, AlphabeticallySorted);
        return GroupMenu->menuAction();
    } else {
        d->addActionToMenu(ToggleViewAction, d->ViewMenu, AlphabeticallySorted);
        return ToggleViewAction;
    }
}

QMenu *DockManager::viewMenu() const
{
    return d->ViewMenu;
}

void DockManager::setViewMenuInsertionOrder(eViewMenuInsertionOrder Order)
{
    d->MenuInsertionOrder = Order;
}

bool DockManager::isRestoringState() const
{
    return d->RestoringState;
}

int DockManager::startDragDistance()
{
    return QApplication::startDragDistance() * 1.5;
}

DockManager::ConfigFlags DockManager::configFlags()
{
    return StaticConfigFlags;
}

DockManager::AutoHideFlags DockManager::autoHideConfigFlags()
{
    return StaticAutoHideConfigFlags;
}

void DockManager::setConfigFlags(const ConfigFlags Flags)
{
    StaticConfigFlags = Flags;
}

void DockManager::setAutoHideConfigFlags(const AutoHideFlags Flags)
{
    StaticAutoHideConfigFlags = Flags;
}

void DockManager::setConfigFlag(eConfigFlag Flag, bool On)
{
    internal::setFlag(StaticConfigFlags, Flag, On);
}

void DockManager::setAutoHideConfigFlag(eAutoHideFlag Flag, bool On)
{
    internal::setFlag(StaticAutoHideConfigFlags, Flag, On);
}

bool DockManager::testConfigFlag(eConfigFlag Flag)
{
    return configFlags().testFlag(Flag);
}

bool DockManager::testAutoHideConfigFlag(eAutoHideFlag Flag)
{
    return autoHideConfigFlags().testFlag(Flag);
}

DockIconProvider &DockManager::iconProvider()
{
    static DockIconProvider Instance;
    return Instance;
}

void DockManager::notifyWidgetOrAreaRelocation(QWidget *DroppedWidget)
{
    if (d->FocusController) {
        d->FocusController->notifyWidgetOrAreaRelocation(DroppedWidget);
    }
}

void DockManager::notifyFloatingWidgetDrop(DockFloatingContainer *FloatingWidget)
{
    if (d->FocusController) {
        d->FocusController->notifyFloatingWidgetDrop(FloatingWidget);
    }
}

void DockManager::setDockWidgetFocused(DockWidget *dockWidget)
{
    if (d->FocusController) {
        d->FocusController->setDockWidgetFocused(dockWidget);
    }
}

void DockManager::hideManagerAndFloatingWidgets()
{
    hide();

    d->HiddenFloatingWidgets.clear();
    // Hide updates of floating widgets from user
    for (auto FloatingWidget : d->FloatingWidgets) {
        if (FloatingWidget->isVisible()) {
            QList<DockWidget *> VisibleWidgets;
            for (auto dockWidget : FloatingWidget->dockWidgets()) {
                if (dockWidget->toggleViewAction()->isChecked())
                    VisibleWidgets.push_back(dockWidget);
            }

            // save as floating widget to be shown when DockManager will be shown back
            d->HiddenFloatingWidgets.push_back(FloatingWidget);
            FloatingWidget->hide();

            // hidding floating widget automatically marked contained CDockWidgets as hidden
            // but they must remain marked as visible as we want them to be restored visible
            // when DockManager will be shown back
            for (auto dockWidget : VisibleWidgets) {
                dockWidget->toggleViewAction()->setChecked(true);
            }
        }
    }
}

DockWidget *DockManager::focusedDockWidget() const
{
    if (!d->FocusController) {
        return nullptr;
    } else {
        return d->FocusController->focusedDockWidget();
    }
}

QList<int> DockManager::splitterSizes(DockAreaWidget *ContainedArea) const
{
    if (ContainedArea) {
        auto Splitter = internal::findParent<DockSplitter *>(ContainedArea);
        if (Splitter) {
            return Splitter->sizes();
        }
    }
    return QList<int>();
}

void DockManager::setSplitterSizes(DockAreaWidget *ContainedArea, const QList<int> &sizes)
{
    if (!ContainedArea) {
        return;
    }

    auto Splitter = internal::findParent<DockSplitter *>(ContainedArea);
    if (Splitter && Splitter->count() == sizes.count()) {
        Splitter->setSizes(sizes);
    }
}

DockFocusController *DockManager::dockFocusController() const
{
    return d->FocusController;
}

void DockManager::setFloatingContainersTitle(const QString &Title)
{
    FloatingContainersTitle = Title;
}

QString DockManager::floatingContainersTitle()
{
    if (FloatingContainersTitle.isEmpty())
        return qApp->applicationDisplayName();

    return FloatingContainersTitle;
}

QX_END_NAMESPACE
